/*
 *
 *  Copyright (C) 2010 Tal Eisenberg
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package tal.drivechain.chain.shell;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.StringTokenizer;
import tal.drivechain.chain.AbortChainException;
import tal.drivechain.chain.Chain;
import tal.drivechain.chain.FreePatcher;
import tal.drivechain.chain.ParallelPatcher;
import tal.drivechain.chain.SerialPatcher;
import tal.drivechain.chain.script.ScriptCompiler;
import tal.drivechain.system.Globals;

/**
 *
 * @author eisental
 */
public class ChainObject extends ShellObject {

    public ChainObject(DriveShell shell) {
        super("chain", "chains", "chain", shell);
    }

    @Override
    public boolean isActionSupported(Action action) {
        return (action!=Action.set && action!=Action.remove && action!=Action.inspect && action!=Action.list);
    }

    @Override
    public void make(String command) throws IllegalArgumentException {
        String chainName = null;
        String patcherName = null;
        StringTokenizer tokenizer = new StringTokenizer(command);
        if (tokenizer.countTokens()==1) {
            patcherName = "free";
            chainName = command;
        } else if (tokenizer.countTokens()==2) {
            patcherName = tokenizer.nextToken();
            chainName = tokenizer.nextToken();
        } else throw new IllegalArgumentException("Invalid syntax (should be: create chain [patcher] <name> )");

        out.println("Creating chain '" + chainName + "'");
        Chain newChain = new Chain(chainName);
        shell.getActiveChain().addChild(newChain);
        out.println("Setting patcher to: " + patcherName);
        if (patcherName==null || patcherName.equals("parallel"))
            newChain.setPatcher(new ParallelPatcher());
        else if (patcherName.equals("serial"))
            newChain.setPatcher(new SerialPatcher());
        else if (patcherName.equals("free"))
            newChain.setPatcher(new FreePatcher());
        else {
            newChain.destroy();
            throw new IllegalArgumentException("Unknown patcher name: " + patcherName);
        }
        newChain.addDefaultMeta();
        shell.setActiveChain(newChain);
    }

    @Override
    public void list() throws IllegalArgumentException {
    }

    public void dump(String command) throws IllegalArgumentException {
        Chain c = Globals.findChain(command, shell.getActiveChain());
        if (c==null)
            throw new IllegalArgumentException("Unknown chain '" + command + "'");
        out.println(c.generateScript());
    }

    public void save(String command) throws IllegalArgumentException {
        try {
            // save chain x to <filename>
            String[] split = command.split(" to ");
            if (split.length!=2)
                throw new IllegalArgumentException("Invalid syntax for save chain command (should be: save chain <chainname> to <filename> )");
            Chain c = Globals.findChain(split[0], shell.getActiveChain());

            if (!Chain.isNodeNameLegal(c.getName())) throw new IllegalArgumentException("Can't save chain " + c.getName() + ". Chain name can't be used in a script file.");
            File file = ScriptCompiler.getScriptFile(split[1], shell.getActiveChain().getScriptFilePath(), true);
            if (file.exists())
                out.println("Overwriting " + file.getCanonicalPath() + " with chain "  + c.getName() + ".");
            else out.println("Saving chain " + c.getName() + " to " + file.getCanonicalPath());

            c.addDefaultMeta(); // adds/updates depends-on property
            
            boolean oldFromFile = c.isLoadedFromFile;
            c.isLoadedFromFile = false; // so the dump will use the chain as embedded even if it's not.

            Writer output = new BufferedWriter(new FileWriter(file));
            try {
                output.write(c.generateScript());
            }
            finally {
                output.close();
                c.isLoadedFromFile = oldFromFile;
            }
            c.setScriptFilePath(file); // store the new filename in the chain object for later use.
        } catch (IOException ex) {
            throw new IllegalArgumentException("while saving chain: " + ex.getMessage());
        }
    }

    public void load(String command) throws IllegalArgumentException {
        // load chain from <filename>;
        String newChainName = null;
        if (!command.startsWith("from ")) {
            int fromIndex = command.indexOf("from ");
            if (fromIndex==-1)
                throw new IllegalArgumentException("Invalid syntax for load chain (should be: load chain [new-name] from <filename> )");
            else {
                newChainName = command.substring(0, fromIndex).trim();
                command = command.substring(fromIndex).trim();
            }
        }

        String filename = command.substring(5).trim();
        String chainInFile = null;
        int atIdx = filename.indexOf("@");
        if (atIdx==-1) chainInFile = null;
        else {
            chainInFile = filename.substring(atIdx+1).trim();
            filename = filename.substring(0, atIdx).trim();
        }

        ScriptCompiler s = new ScriptCompiler();
        s.loadFromFile(ScriptCompiler.getScriptFile(filename, shell.getActiveChain().getScriptFilePath(), false));
        s.parseScript();
        Chain[] chains = null;
        try {
            chains = s.compileScript(null);

            if (chainInFile==null && newChainName!=null && chains.length>1)
                throw new IllegalArgumentException("Script file " + filename + " has more than one chain. Can't load multiple chains with the same name.");
            else if (chainInFile==null && newChainName==null && chains.length>1) {
                // means a batch load of a couple of chains.
                for (Chain c : chains) {
                    c.isLoadedFromFile = true;
                    shell.getActiveChain().addChild(c);
                    out.println("added chain " + c.getPath() + ".");
                }
            } else if (chainInFile!=null) {
                Chain retChain = null;
                for (Chain chain : chains) {
                    if (chain.getName().equals(chainInFile)) retChain = chain;
                    else chain.destroy();
                }
                if (retChain==null) throw new IllegalArgumentException("Can't find chain " + chainInFile + " in script file " + filename);
                else {
                    if (newChainName!=null)
                        retChain.setName(newChainName);
                    retChain.isLoadedFromFile = true;
                    shell.getActiveChain().addChild(retChain);
                    out.println("added chain " + retChain.getPath() + ".");
                }
            } else if (chains.length==1) {
                Chain c = chains[0];
                if (newChainName!=null) c.setName(newChainName);
                c.isLoadedFromFile = true;
                shell.getActiveChain().addChild(c);
                out.println("added chain " + c.getPath() + ".");
            }
        } catch (AbortChainException ex) {
            if (chains!=null)
                for (Chain c : chains) { c.destroy(); }
            ex.printStackTrace();
            throw new IllegalArgumentException(ex);
        } catch (IllegalArgumentException ex) {
            if (chains!=null)
                for (Chain c : chains) { c.destroy(); }
            ex.printStackTrace();
            throw ex;
        }
    }

    public void embed(String command) throws IllegalArgumentException {
        Chain c = Globals.findChain(command, shell.getActiveChain());
        if (c==null)
            throw new IllegalArgumentException("Unknown chain '" + command + "'");
        if (!c.isLoadedFromFile) throw new IllegalArgumentException("Chain " + c.getPath() + " is already embedded in chain " + c.getParentChain().getPath());
        c.isLoadedFromFile = false;
        out.println("Embedded chain " + command + ".");
    }

    public void unembed(String command) throws IllegalArgumentException {
        Chain c = Globals.findChain(command, shell.getActiveChain());
        if (c==null)
            throw new IllegalArgumentException("Unknown chain '" + command + "'");
        if (c.isLoadedFromFile) throw new IllegalArgumentException("Chain " + c.getPath() + " is not embedded in chain " + c.getParentChain().getPath());
        c.isLoadedFromFile = true;
        out.println("Unembedded chain " + command + ".");
    }

    @Override
    public void inspect(String command) throws IllegalArgumentException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void remove(String command)  throws IllegalArgumentException {
    }

    @Override
    public void set(String command)  throws IllegalArgumentException {
        throw new UnsupportedOperationException("Not supported.");
    }

}
